#!/usr/bin/python
#
# DESCRIPTION
#
#   This script implements the query for the external application, Linux/UNIX.  
#
#   The query command is an input command or event. A query is used to find 
#   and read information about entries in the external application, therefore
#   it is both a "search" and a "read" operation.
#
#
# VARIABLES
#
#   SCOPE
#     Specifies the extent of the search.  This attribute supports
#     the following values:  
#       * subtree - indicates to search the base entry and all entries
#         in its branch of the directory tree.  If no scoope is specified,
#         subtree is used as the default value.
#       * subordinates - indicates to search the immediate subordinates of 
#         the base entry (the base entry is not searched).
#       * entry - indicates to search just the base entry.
#     For scopes other than entry, the selected entries can be further
#     limited by the search_classES and SEARCH_ATTR_ elements.  For scopes of
#     entry, the search_classES and SEARCH_ATTR_ elements are ignored.
#     
#   DEST_DN
#     Specifies the distinguished name for the starting point for the search.
#     If both the DEST_DN attribute and ASSOCIATION have values, the 
#     ASSOCIATION value is used as the starting point for the search.  If 
#     neither have values, the search begins at the root of the directory.
#
#   CLASS_NAME
#     Specififes the base class of the DEST_DN attribute.
#
#   EVENT_ID
#     Specifies an identifier used to identify a particular instance of the 
#     command or event.
#
#   ASSOCIATION
#     Specifies the unique identifier for the entry where the search begins.  
#     If  both the DEST_DN attribute and the ASSOCIATION have values, the 
#     ASSOCIATION value is used as the starting point for the search.  If 
#     neither have values, the search begins at the root of the directory.
#
#   search_classES
#     Specifies the search filter for object classes.  If the query contains no
#     search_classES elements, all entries matching the scope and the 
#     SEARCH_ATTR_ elements are returned.
# 
#   SEARCH_ATTRS
#     Contains a list of the SEARCH_ATTR_ attribute names.
#
#   SEARCH_ATTR_<ATTR_NAME>
#     Specifies the search filter for attribute values.  If more than one 
#     SEARCH_ATTR_ element is specified, the entry must match all attributes
#     to be returned.  
#
#     <ATTR_NAME> will be replaced by the literal name of the attribute, 
#     upper-cased and non-printable characters converted to underscores.
#
#   READ_ATTRS
#     Specifies which attribute values are returned with entries that match
#     the search filters.
#
#   ALL_READ_ATTRS
#     Specifies that all readable attributes should be returned.
#
#   NO_READ_ATTRS
#     Specifies that no attributes are to be returned.
#
#   READ_PARENT
#     Specifies whether the parent of the entry is returned with the entry.
#
#
# REPLY FORMAT
#
#   The receiving application should respond to the query with an INSTANCE 
#   command for each entry returned.  The response should also include a
#   status indicating whether the query was processed successfully.
#   A query should return a successful status even when no entries exist
#   that match the search criteria.
#
#   The format for the INSTANCE command is as follows:
#
#     idmsetvar("COMMAND" "INSTANCE")        (zero or more)
#     idmsetvar("CLASS_NAME", class-name)    (mandatory)
#     idmsetvar("SRC_DN", src-dn)            (optional)
#     idmsetvar("ASSOCIATION", association)  (optional)
#     idmsetvar("PARENT", parent)            (optional)
#     idmsetvar("ATTR_attribute", value)     (zero or more)
#
#    The format for returning STATUS and STATUS_MESSAGE are as follows:
#
#      status_<level>("<optional message>")
#
#        <level> may be one of the following values:
#        * success
#        * warning
#        * error
#        * retry
#        * fatal
#
#      Note:  fatal will shutdown the driver, retry will retry the event
#             later on.
#

# include the IDM Library
from idmlib import *
from globals import *

import MySQLdb
import sys


def check_connect_info():
  if (dbname == ''):
      status_error("Missing database name to delete!")
      sys.exit(0)
  if (dbhost == ''):
      status_error("Missing database host!")
      sys.exit(0)
  if (dbport == ''):
      status_error("Missing database port!")
      sys.exit(0)
  if (mysqlprovuser == ''):
      status_error("Missing mysql provisioning user!")
      sys.exit(0)
  if (mysqlprovpw == ''):
      status_error("Missing mysql provisioning password!")
      sys.exit(0)

def get_db_connection():
  try:
      conn = MySQLdb.connect (host = dbhost,
                             user = mysqlprovuser,
                             passwd = mysqlprovpw,
                             port = dbport)

  except MySQLdb.Error, e:
    status_error("MYSQL ERROR %d: %s" % (e.args[0], e.args[1]))
    sys.exit (0)
  except KeyError:
    status_error("Key Error:  Most likely cause is using an invalid dbhost, or \
    missing privileged user/pass for dbhost")
    sys.exit (0)
  except:
    status_error("An unexpected error occured while trying to connect mysql on: " + dbhost)
    sys.exit(0)
  return conn 

def check_if_db_exists():
       logger("debug", "query.py", " looking in the database server ")
       global found
       try:
          cursor = conn.cursor ()
          cursor.execute ("SHOW DATABASES LIKE '" + MySQLdb.escape_string(dbname) + "'");
          if (cursor.rowcount > 0):
             found = 'true'
          cursor.close ()

       except MySQLdb.Error, e:
          cursor.close ()
          status_error("MYSQL ERROR %d: %s" % (e.args[0], e.args[1]))
          logger("debug", "query.py", " failed with db error")
          sys.exit (0)
###Main

g =  GlobalSettings()

logger("debug", "query.py", " querying ")
trace(" query.py ")

# retrieve the provisioning user and password from the driver
mysqlprovuser = idmgetdrvvar("mysqlprovuser");
mysqlprovpw = idmsubgetnamedpassword("mysqlprovpw");

# retrieve information from the query event
scope = idmgetvar("SCOPE")
class_name = idmgetvar("CLASS_NAME") ### should always be 'mysqlDatabase'
association = idmgetvar("ASSOCIATION")
dest_dn = idmgetvar("DEST_DN")
search_classes = idmgetvar("SEARCH_CLASS")
event_id = idmgetvar("EVENT_ID")
read_attrs = idmgetvar("READ_ATTRS")

# check the query scope
if scope == "entry":
   
  logger("debug", "query.py", " scope is entry ")
  # entry scope queries ask about a particular object

  # check for an association, if the object has already 
  # been associated
  if association != "":
    # the association was created by the scripts and should
    # be sufficient in determining this particular object's
    # class type (class_name).

    # the search base for our query is the association for
    # the sample skeleton scripts
    search_base = association
  else:
    # without an association, we can use the DEST_DN field to
    # determine the search base for our query
    search_base = dest_dn

  # now we should have a search base determined
  if search_base != "":

    # INSERT CUSTOM CODE HERE
    #
    # Read the object search_base which identifies the object
    # name we're interested in reading.  Create an association
    # string for this object that can be used to uniquely identify
    # the object.

    # First, look to see if the search_base is an association.  Since our associations
    # are of the form: dbname@dhbhost@dbport, we know which server to look at.

    # We haven't found anything yet
    found = 'false';
    found_dbhost = '';
    found_dbport = '';

    # If our search base is an association
    if (search_base.count('@') == 2 ):

       # We have an association, so lets break it apart
       (dbname,dbhost,dbport) = search_base.split('@')
       
       # This will be needed as an int later
       dbport = int(dbport)

       # Do a validity check
       check_connect_info()

       # Get a database connection
       conn = get_db_connection()    

       # Does the database exist where we think it does?
       check_if_db_exists()

       if (found == 'true'):
          found_dbhost = dbhost
          found_dbport = dbport

    else:  # our search base is just dbname 

       dbname = search_base

       # lets loop through all our provisionable database servers and 
       # see if we can find it by dbname
       for dbhost in g.provisionable_servers.keys():
           dbport = g.provisionable_servers[dbhost]['port'] 
 
           # Do a validity check
           check_connect_info()

           # Get a database connection
           conn = get_db_connection()

           # Does the database exist where we think it does?
           check_if_db_exists()

           if (found == 'true'):
              found_dbhost = dbhost
              found_dbport = dbport
              break

       ## End of for 
    ### End of else

    ## If we found it
    if (found == 'true'):

      logger("debug", "query.py", " found it ")
      # if the association doesn't exist, make one
      if (association.count('@') != 2 ):
         association = dbname + '@' + found_dbhost + '@' + str(found_dbport)

      # If the object is found, return:
      idmsetvar("COMMAND", "instance")
      idmsetvar("EVENT_ID", event_id)
      idmsetvar("SRC_DN", search_base)
      idmsetvar("CLASS_NAME", class_name)
      idmsetvar("ASSOCIATION", association)

      #not the most efficient but that's what refactoring is for
      (returncn,returnhost,returnport) = association.split('@')

      # check for which attributes to return (read)
      all_read_attrs = idmgetvar("ALL_READ_ATTRS")

      if all_read_attrs == "true":

        # return all attributes that can be read
        idmsetvar("ATTR_CN", returncn)
        idmsetvar("ATTR_HOST", returnhost)
        idmsetvar("ATTR_PORT", returnport)
        pass

      else:

        read_attrs = idmgetvar("READ_ATTRS")

        # return only those attributes which are requested
        for read_attr in read_attrs:

          if read_attr == "CN":
            idmsetvar("ATTR_CN", returncn)
          elif read_attr == "HOST":
            idmsetvar("ATTR_HOST", returnhost)
          elif read_attr == "PORT":
            idmsetvar("ATTR_PORT", returnport)

    ##End of if (found == 'true'):
    else:
        trace("query.py: Didn't find it") 
        logger("debug", "query.py", " did not find it ")

  else:
    status_error("Unable to derive a search base")
else:
  
  logger("debug", "query.py", " scope is subtree or subordinate ")
  # we have a subtree or subordinate query search

  if association != "":
    # the association was created by the scripts and should
    # be sufficient in determining this particular object's
    # class type (class_name).

    # the search base for our query is the association for
    # the sample skeleton scripts
    search_base = association
  else:
    # without an association, we can use the DEST_DN field to
    # determine the search base for our query
    search_base = dest_dn



  if (str(search_classes) == ''):
     search_classes = ['sacrificial_search_class_to_get_us_in_the_loop']
  for search_class in search_classes:

    logger("debug", "query.py", " looping through search classes ")
    # INSERT CUSTOM CODE HERE
    # 
    # Search for the object defined by this particular
    # search_class and SEARCH_ATTRS.  Return zero or more
    # instances along with a status document indicating the
    # level of success.
    #
  
    # retrieve the search attributes/values to search on 
    dbname = idmgetvar("SEARCH_ATTR_CN")

    found = 'false'
    found_dbhost = '';
    found_dbport = '';

    # lets loop through all our provisionable database servers and 
    # see if we can find it by dbname
    for dbhost in g.provisionable_servers.keys():
        dbport = g.provisionable_servers[dbhost]['port'] 
 
        # Do a validity check
        check_connect_info()

        # Get a database connection
        conn = get_db_connection()

        # Does the database exist where we think it does?
        check_if_db_exists()

        if (found == 'true'):
           found_dbhost = dbhost
           found_dbport = dbport
           break

    # end for loop

    logger("debug", "query.py", " done looping")
    if (found == 'true'):
       
        logger("debug", "query.py", " found it ")
        #lets make an association
        association = dbname + '@' + found_dbhost + '@' + str(found_dbport)

        # For each entry that matches, return an instance document:
        
        idmsetvar("COMMAND", "instance")
        idmsetvar("EVENT_ID", event_id)
        idmsetvar("SRC_DN", search_base)
        idmsetvar("CLASS_NAME", class_name)
        idmsetvar("ASSOCIATION", association)

        # with each instance document, return all read attrs that were
        # requested by the query:

        # check for which attributes to return (read)
        all_read_attrs = idmgetvar("ALL_READ_ATTRS")
        if all_read_attrs == "true":

          # return all attributes that can be read
          idmsetvar("ATTR_CN", dbname)
          idmsetvar("ATTR_HOST", found_dbhost)
          idmsetvar("ATTR_PORT", found_dbport)
          pass
        else:
          # return only those attributes which are requested
          for read_attr in read_attrs:
            if read_attr == "CN":
              idmsetvar("ATTR_CN", dbname)
            elif read_attr == "HOST":
              idmsetvar("ATTR_HOST", found_dbhost)
            elif read_attr == "PORT":
              idmsetvar("ATTR_PORT", found_dbport)
    else:
        trace("query.py: Didn't find it") 
        logger("debug", "query.py", " did not find it ")
# For the skeleton script, simply return a "Not Implemented" status
status_success("OK")
